#!/usr/bin/perl -w
# File: server.pl
# Usage: server.pl
# Author: Tao Zhang

use strict;
use warnings;
use POSIX qw(:signal_h);
use Proc::Daemon;
use IO::Socket;
use Digest::MD5;
use MLUtils::Logger qw(mllogerr);

# where the file is
my $dir = "/home/t0612z/workspace/filesyn/locker";
my $timeout = 30;

# ------------------subroutines-----------------------------

sub checksum($) {
    my $file = shift;
    my $md5 = Digest::MD5->new();
    
    open(FL, "$dir/$file") 
        or die "Failed to open $file for MD5 checksum. \n";
    binmode(FL);
    my $md5sum = $md5->addfile(*FL)->hexdigest; 
    close FL;
    
    return $md5sum;
}

sub wait_for_ctrstr($) {
    my $server = shift;
    my $input = undef;
    
    eval {
        local $SIG{'ALRM'} = sub { die "Timed out" };
        alarm($timeout);
        $input = <$server>;
        chomp($input);
        alarm(0);
    };
    
    if ($@ =~ /Timed out/) {
        print STDERR "Waitting for control string timed Out.\n";
        $server->close();
        exit -1;
    }
    
    if ($input) {
        return $input;
    } else {
        print STDERR "Failed to read from client.\n";
        $server->close();
        exit -1;
    }
}


sub wait_for_file_content($) {
    my $server = shift;
    my $input = undef;
    
    if ( ! $server->connected() ) {
        print STDERR "Client closed.\n";
        $server->close();
        exit -1;
    }
    
    eval {
        local $SIG{'ALRM'} = sub { die "Timed out" };
        alarm($timeout);
        $input = <$server>;
        alarm(0);
    };
    
    if ($@ =~ /Timed out/) {
        print STDERR "Waitting for control string timed Out.\n";
        $server->close();
        exit -1;
    }
    
    if ($input) {
        return $input;
    } else {
        print STDERR "Failed to read from client.\n";
        $server->close();
        exit -1;
    }
}

sub wait_for_command($) {
    my $server = shift;
    my $command_timeout = 1200;
    my $input = undef;
    
    if ( ! $server->connected() ) {
        print STDERR "Client closed.\n";
        $server->close();
        exit -1;
    }
    
    eval {
        local $SIG{'ALRM'} = sub { die "Timed out" };
        alarm($command_timeout);
        $input = <$server>;
        alarm(0);
    };
    
    if ($@ =~ /Timed out/i) {
        print STDERR "Waitting for control string timed Out.\n";
        $server->close();
        exit -1;
    }
    
    if ($input) {
        return $input;
    } else {
        print STDERR "Failed to read from client.\n";
        $server->close();
        exit -1;
    }
}

# receive the file's content and rewrite into local file
sub receive_file($$$) {
    my $client = shift;
    my $file_name = shift;
    my $file_md5 = shift;
    my $redo = 1;

    while ($redo) {
        $redo++;
                
        if ( ! open(FILE, ">$dir/$file_name") ) {
            close $client;
            print STDERR "Failed to open $file_name. $!\n".
            exit -1;
        }
        
        print $client "MLTRAN:WAIT\n";
        while ( (my $content = wait_for_file_content($client)) !~ /MLTRAN:EOF/i ) {
            print FILE $content;
        }
        close FILE;

        if (checksum($file_name) eq $file_md5) {
            print $client "MLTRAN:SUCC\n";
            if ( wait_for_file_content($client) =~ /MLTRAN:QUIT/ ) {
                exit 0;
            }
        } else {
            # we only try three time, after that inform client to quit
            if ($redo > 4) {
                print $client "MLTRAN:NOMORETRY\n";
                $redo = 0;
            } else {
                print $client "MLTRAN:FAIL\n";
            }  
        }
    }
}

# receive the file's content and rewrite into local file
sub receive_commands($) {
    my $client = shift;
    
    while ( (my $str = wait_for_command($client)) !~ /QUIT/i) {
        chomp $str;
        if ( open(CMD, "$str 2>&1 |") ) {
            while (<CMD>) {
                print $client $_;
            }
            print $client "MLTRAN:EOC\n";
            close CMD;
        } else {
          print $client "$!, please check your CMD <$str>\n";
          print $client "MLTRAN:EOC\n";
        }
    }
    $client->close();
}


# ------------------main------------------------------------
$SIG{CHLD} = 'IGNORE';
my $server = IO::Socket::INET->new(
    Listen => 5,
    LocalAddr => '10.10.0.203',
    LocalPort => 8000,
    Proto     => 'tcp',
    Listen => 5,
    Reuse => 1,
) or die "Can't create server socket: $!";

while (my $client = $server->accept) {
    my $pid = fork();
    
    if ($pid < 0) {
        die "Fails to fork:$!";
    }
    
    if ($pid > 0) {
        close $client;
    } else {
        close $server;
        my $str = wait_for_ctrstr($client);
       
        if ($str =~ /MLTRAN:FILE:(\S+):(\S+)/i) {  # receive a configure file
            receive_file($client, $1,$2);
            exit 0;
        } elsif ($str =~ /MLTRAN:COMMAND/i) { #  receive commands
            receive_commands($client);
            exit 0;
        } else { # some unidentified client
            close $client;
            exit 0;
        }
    }
}

exit 0;
